/*
 * Created on Jun 16, 2014
 * Created by Paul Gardner
 * 
 * Copyright 2014 Azureus Software, Inc.  All rights reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License only.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

package org.gudy.azureus2.core3.download.impl;

import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.gudy.azureus2.core3.download.DownloadManagerAvailability;
import org.gudy.azureus2.core3.peer.PEPeerSource;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentAnnounceURLSet;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerDataProvider;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerFactory;
import org.gudy.azureus2.core3.util.AENetworkClassifier;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.core3.util.TorrentUtils;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.ipc.IPCException;
import org.gudy.azureus2.plugins.ipc.IPCInterface;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;

import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.tracker.TrackerPeerSource;
import com.aelitis.azureus.core.tracker.TrackerPeerSourceAdapter;
import com.aelitis.azureus.plugins.extseed.ExternalSeedPlugin;
import com.aelitis.azureus.plugins.tracker.dht.DHTTrackerPlugin;

public class DownloadManagerAvailabilityImpl implements DownloadManagerAvailability {
    private List<TrackerPeerSource> peer_sources = new ArrayList<TrackerPeerSource>();

    private TRTrackerAnnouncer tracker_client;

    public DownloadManagerAvailabilityImpl(final TOTorrent to_torrent, List<List<String>> updated_trackers, final String[] _enabled_peer_sources,
            final String[] _enabled_networks) {
        if (to_torrent == null) {

            return;
        }

        final Set<String> enabled_peer_sources = new HashSet<String>(Arrays.asList(_enabled_peer_sources));
        final Set<String> enabled_networks = new HashSet<String>(Arrays.asList(_enabled_networks));

        Torrent torrent = PluginCoreUtils.wrap(to_torrent);

        if (enabled_peer_sources.contains(PEPeerSource.PS_BT_TRACKER)) {

            TOTorrentAnnounceURLSet[] sets;

            if (updated_trackers == null) {

                sets = to_torrent.getAnnounceURLGroup().getAnnounceURLSets();

            } else {

                sets = TorrentUtils.listToAnnounceSets(updated_trackers, to_torrent);
            }

            if (sets.length == 0) {

                sets =
                        new TOTorrentAnnounceURLSet[] { to_torrent.getAnnounceURLGroup().createAnnounceURLSet(
                                new URL[] { to_torrent.getAnnounceURL() }) };
            }

            try {
                tracker_client = TRTrackerAnnouncerFactory.create(to_torrent, new TRTrackerAnnouncerFactory.DataProvider() {
                    public String[] getNetworks() {
                        return (_enabled_networks);
                    }
                });

                tracker_client.setAnnounceDataProvider(new TRTrackerAnnouncerDataProvider() {
                    public String getName() {
                        return ("Availability checker");
                    }

                    public long getTotalSent() {
                        return (0);
                    }

                    public long getTotalReceived() {
                        return (0);
                    }

                    public long getRemaining() {
                        return (to_torrent.getSize());
                    }

                    public long getFailedHashCheck() {
                        return (0);
                    }

                    public String getExtensions() {
                        return (null);
                    }

                    public int getMaxNewConnectionsAllowed() {
                        return (1); // num-want -> 1
                    }

                    public int getPendingConnectionCount() {
                        return (0);
                    }

                    public int getConnectedConnectionCount() {
                        return (0);
                    }

                    public int getUploadSpeedKBSec(boolean estimate) {
                        return (0);
                    }

                    public int getCryptoLevel() {
                        return (NetworkManager.CRYPTO_OVERRIDE_NONE);
                    }

                    public boolean isPeerSourceEnabled(String peer_source) {
                        return (true);
                    }

                    public void setPeerSources(String[] allowed_sources) {
                    }
                });

                tracker_client.update(true);

            } catch (Throwable e) {

                Debug.out(e);
            }

            // source per set

            for (final TOTorrentAnnounceURLSet set : sets) {

                final URL[] urls = set.getAnnounceURLs();

                if (urls.length == 0 || TorrentUtils.isDecentralised(urls[0])) {

                    continue;
                }

                peer_sources.add(new TrackerPeerSource() {
                    private TrackerPeerSource _delegate;

                    private TRTrackerAnnouncer ta;
                    private long ta_fixup;

                    private long last_scrape_fixup_time;
                    private Object[] last_scrape;

                    private TrackerPeerSource fixup() {
                        long now = SystemTime.getMonotonousTime();

                        if (now - ta_fixup > 1000) {

                            TRTrackerAnnouncer current_ta = tracker_client;

                            if (current_ta == ta) {

                                if (current_ta != null && _delegate == null) {

                                    _delegate = current_ta.getTrackerPeerSource(set);
                                }
                            } else {

                                if (current_ta == null) {

                                    _delegate = null;

                                } else {

                                    _delegate = current_ta.getTrackerPeerSource(set);
                                }

                                ta = current_ta;
                            }

                            ta_fixup = now;
                        }

                        return (_delegate);
                    }

                    protected Object[] getScrape() {
                        long now = SystemTime.getMonotonousTime();

                        if (now - last_scrape_fixup_time > 30 * 1000 || last_scrape == null) {

                            last_scrape = new Object[] { -1, -1, -1, -1, -1, "" };

                            last_scrape_fixup_time = now;
                        }

                        return (last_scrape);
                    }

                    public int getType() {
                        return (TrackerPeerSource.TP_TRACKER);
                    }

                    public String getName() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return (urls[0].toExternalForm());
                        }

                        return (delegate.getName());
                    }

                    public int getStatus() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return (ST_STOPPED);
                        }

                        return (delegate.getStatus());
                    }

                    public String getStatusString() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return ((String) getScrape()[5]);
                        }

                        return (delegate.getStatusString());
                    }

                    public int getSeedCount() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return ((Integer) getScrape()[0]);
                        }

                        int seeds = delegate.getSeedCount();

                        if (seeds < 0) {

                            seeds = (Integer) getScrape()[0];
                        }

                        return (seeds);
                    }

                    public int getLeecherCount() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return ((Integer) getScrape()[1]);
                        }

                        int leechers = delegate.getLeecherCount();

                        if (leechers < 0) {

                            leechers = (Integer) getScrape()[1];
                        }

                        return (leechers);
                    }

                    public int getCompletedCount() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return ((Integer) getScrape()[4]);
                        }

                        int comp = delegate.getCompletedCount();

                        if (comp < 0) {

                            comp = (Integer) getScrape()[4];
                        }

                        return (comp);
                    }

                    public int getPeers() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return (-1);
                        }

                        // we only asked for 1 peer so if we have a scrape value then we'll leave this blank
                        // to avoid confusion

                        if (delegate.getSeedCount() > 0 || delegate.getLeecherCount() > 0) {

                            return (-1);
                        }

                        return (delegate.getPeers());
                    }

                    public int getInterval() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            Object[] si = getScrape();

                            int last = (Integer) si[2];
                            int next = (Integer) si[3];

                            if (last > 0 && next < Integer.MAX_VALUE && last < next) {

                                return (next - last);
                            }

                            return (-1);
                        }

                        return (delegate.getInterval());
                    }

                    public int getMinInterval() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return (-1);
                        }

                        return (delegate.getMinInterval());
                    }

                    public boolean isUpdating() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return (false);
                        }

                        return (delegate.isUpdating());
                    }

                    public int getLastUpdate() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return ((Integer) getScrape()[2]);
                        }

                        return (delegate.getLastUpdate());
                    }

                    public int getSecondsToUpdate() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return (-1);
                        }

                        return (delegate.getSecondsToUpdate());
                    }

                    public boolean canManuallyUpdate() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate == null) {

                            return (false);
                        }

                        return (delegate.canManuallyUpdate());
                    }

                    public void manualUpdate() {
                        TrackerPeerSource delegate = fixup();

                        if (delegate != null) {

                            delegate.manualUpdate();
                        }
                    }

                    public boolean canDelete() {
                        return (false);
                    }

                    public void delete() {
                    }
                });
            }
        }

        // http seeds

        try {
            ExternalSeedPlugin esp = DownloadManagerController.getExternalSeedPlugin();

            if (esp != null) {

                TrackerPeerSource ext_ps = esp.getTrackerPeerSource(torrent);

                if (ext_ps.getSeedCount() > 0) {

                    peer_sources.add(ext_ps);
                }
            }
        } catch (Throwable e) {
        }

        if (enabled_peer_sources.contains(PEPeerSource.PS_DHT) && enabled_networks.contains(AENetworkClassifier.AT_PUBLIC)) {

            // dht

            if (!torrent.isPrivate()) {

                try {

                    PluginInterface dht_pi = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByClass(DHTTrackerPlugin.class);

                    if (dht_pi != null) {

                        peer_sources.addAll(Arrays.asList(((DHTTrackerPlugin) dht_pi.getPlugin()).getTrackerPeerSources(torrent)));
                    }
                } catch (Throwable e) {
                }
            }
        }

        if (enabled_peer_sources.contains(PEPeerSource.PS_DHT)) {
            // enabled_networks.contains( AENetworkClassifier.AT_I2P )){

            // always do this availability check for the moment

            if (!torrent.isPrivate()) {

                try {
                    PluginInterface i2p_pi = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByID("azneti2phelper", true);

                    if (i2p_pi != null) {

                        IPCInterface ipc = i2p_pi.getIPC();

                        Map<String, Object> options = new HashMap<String, Object>();

                        options.put("peer_networks", _enabled_networks);

                        final int[] lookup_status = new int[] { TrackerPeerSource.ST_INITIALISING, -1, -1, -1 };

                        IPCInterface callback = new IPCInterface() {
                            public Object invoke(String methodName, Object[] params)

                            throws IPCException {
                                synchronized (lookup_status) {

                                    lookup_status[0] = (Integer) params[0];

                                    if (params.length >= 4) {

                                        lookup_status[1] = (Integer) params[1];
                                        lookup_status[2] = (Integer) params[2];
                                        lookup_status[3] = (Integer) params[3];
                                    }
                                }

                                return (null);
                            }

                            public boolean canInvoke(String methodName, Object[] params) {
                                return (true);
                            }
                        };

                        TrackerPeerSource ps = new TrackerPeerSourceAdapter() {
                            public int getType() {
                                return (TP_DHT);
                            }

                            public String getName() {
                                return ("I2P DHT");
                            }

                            public int getStatus() {
                                synchronized (lookup_status) {

                                    return (lookup_status[0]);
                                }
                            }

                            public int getSeedCount() {
                                synchronized (lookup_status) {

                                    int seeds = lookup_status[1];
                                    int peers = lookup_status[3];

                                    if (seeds == 0 && peers > 0) {

                                        return (-1);
                                    }

                                    return (seeds);
                                }
                            }

                            public int getLeecherCount() {
                                synchronized (lookup_status) {

                                    int leechers = lookup_status[2];
                                    int peers = lookup_status[3];

                                    if (leechers == 0 && peers > 0) {

                                        return (-1);
                                    }

                                    return (leechers);
                                }
                            }

                            public int getPeers() {
                                synchronized (lookup_status) {

                                    int peers = lookup_status[3];

                                    return (peers == 0 ? -1 : peers);
                                }
                            }
                        };

                        ipc.invoke("lookupTorrent", new Object[] { "Availability lookup for '" + torrent.getName() + "'", torrent.getHash(),
                                options, callback });

                        peer_sources.add(ps);
                    }
                } catch (Throwable e) {
                }
            }
        }
    }

    public List<TrackerPeerSource> getTrackerPeerSources() {
        return (peer_sources);
    }

    public void destroy() {
        if (tracker_client != null) {

            tracker_client.destroy();
        }
    }
}
